<?php

/**
 * Kaeru Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2009, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

/**
 * The AbstractModel contains methods for handling properties and
 * setting/getting validation messages/info.
 */
abstract class AbstractModel
{
    /**
     * Is set to true or false if the model is validated.
     *
     * @var boolean
     */
    protected $isValid;

    /**
     * Is filled with validation messages if the model has been validated and
     * it isn't valid.
     *
     * @var array
     */
    protected $validationMessages = array();

    /**
     * "Magic" convenience method for those who prefers using setters and
     * getters. Note that using this method instead of accessing public
     * properties directly is much slower.
     *
     * Examples:
     * setMyProperty($value) - sets $value as the property 'myProperty''s value
     * getMyProperty() - gets the property 'myProperty''s value
     * unsMyProperty() - unsets the property 'myProperty''s value
     * hasMyProperty() - returns true if the model has the property 'myProperty'
     *
     * @param string $method
     * @param array $args
     * @return mixed
     */
    public function __call($method, $args)
    {
        $property = lcfirst(substr($method, 3));
        switch (substr($method, 0, 3)) {
            case 'get':
                return $this->getProperty($property);
            case 'set':
                if (count($args) == 1) {
                    $args = $args[0];
                }
                $this->$property = $args;
                return $this;
            case 'uns':
                unset($this->$property);
            case 'has':
                return property_exists($this, $property);
        }
    }

    /**
     * "Magic" method for getting private/protected property values.
     *
     * @param string $property
     * @return mixed
     */
    public function __get($property)
    {
        return $this->getProperty($property);
    }

    /**
     * "Magic" method to decide if a private/protected property is set by using
     * the 'isset()' function.
     *
     * @param string $property
     * @return boolean
     */
    public function __isset($property)
    {
        return isset($this->$property);
    }

    /**
     * "Magic" method for setting private/protected property values.
     *
     * @param string $property
     * @param mixed $value
     * @return void
     * @todo Log if no property is defined with $property as name.
     */
    public function __set($property, $value)
    {
        if (!property_exists($this, $property)) {
            // TODO: ::getLogger()->warning('Property is not defined.', $this);
        }
        $this->$property = $value;
    }

    /**
     * "Magic" method to unset a private/protected property by using the
     * 'unset()' function.
     *
     * @param string $property
     * @return void
     */
    public function __unset($property)
    {
        unset($this->$property);
    }

    /**
     * Returns the model properties as an associative array.
     *
     * @param boolean $publicOnly
     * @return array
     */
    public function getProperties($publicOnly = false)
    {
        if ($publicOnly) {
            $reflection = new ReflectionClass($this);
            $reflectionProperties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);

            $publicProperties = array();
            foreach ($reflectionProperties as $reflectionProperty) {
                $property = $reflectionProperty->name;
                $publicProperties[$property] = $this->$property;
            }
            return $publicProperties;
        }
        return get_object_vars($this);
    }

    /**
     * Gets the value of a property. If the property is an instance of
     * \Kaeru\Model\Property\PropertyProxyInterface, the 'execute' method will be called
     * before returning the property value.
     *
     * @param string $property
     * @return mixed
     */
    public function getProperty($property)
    {
        if (!property_exists($this, $property)) {
            return null;
        }

        if ($this->$property instanceof IPropertyProxy) {
            $this->$property->execute($property, $this);
        }
        return $this->$property;
    }

    /**
     * Gets any validation messages.
     *
     * @return array
     */
    public function getValidationMessages()
    {
        return $this->validationMessages;
    }

    /**
     * Returns true if the model is valid, false if it isn't.
     *
     * @return boolean
     */
    public function isValid()
    {
        return (boolean) $this->isValid;
    }

    /**
     * Returns true if the model has been validated, else it returns false.
     *
     * @return boolean
     */
    public function isValidated()
    {
        if ($this->isValid === null) {
            return false;
        }
        return true;
    }

    /**
     * Sets property values from an associative array where the key should match
     * the property name and the value is the property value.
     *
     * @param array $properties
     * @return \Kaeru\Model\AbstractModel
     */
    public function setProperties(array $properties)
    {
        foreach ($properties as $property => $value) {
            $this->$property = $value;
        }
        return $this;
    }

    /**
     * Sets whether the model is valid or not.
     *
     * @param boolean $isValid
     * @return \Kaeru\Model\AbstractModel
     */
    public function setIsValid($isValid = true)
    {
        $this->isValid = (boolean) $isValid;
        return $this;
    }

    /**
     * Sets any validation messages.
     *
     * @param array $messages
     * @return \Kaeru\Model\AbstractModel
     */
    public function setValidationMessages(array $messages)
    {
        $this->validationMessages = $messages;
        return $this;
    }
}